package ltd.hello.trik.snake;

import processing.core.PApplet;

import java.util.Arrays;
import java.util.stream.IntStream;

/**
 * +----------------------------------------------------------------------
 * |
 * +----------------------------------------------------------------------
 * | <a href="https://www.hellotrik.ltd">Copyright (c) 2024~2025 </a>.
 * +----------------------------------------------------------------------
 * | Licensed MIT
 * +----------------------------------------------------------------------
 * | 作者: 🦠 matrixg 👾 <hellotrik@foxmail.com>
 * | 日期: 2024/9/6
 * +----------------------------------------------------------------------
 */
public class Population extends SnakeCommon {
    Snake[] snakes;
    Snake bestSnake;

    int bestSnakeScore = 0;
    int gen = 0;
    int samebest = 0;

    float bestFitness = 0;
    float fitnessSum = 0;

    public Population(int size, PApplet applet) {
        super(applet);
        snakes = new Snake[size];
        for (int i = 0; i < snakes.length; i++) {
            snakes[i] = new Snake(applet);
        }
        bestSnake = snakes[0].clone();
        bestSnake.replay = true;
    }

    public boolean done() {  //check if all the snakes in the population are dead
        return Arrays.stream(snakes).parallel().allMatch(Snake::isDead)&&bestSnake.isDead();
    }

    public void update() {  //update all the snakes in the generation
        if (!bestSnake.dead) {  //if the best snake is not dead update it, this snake is a replay of the best from the past generation
            bestSnake.look();
            bestSnake.think();
            bestSnake.move();
        }
        Arrays.stream(snakes).filter(x->!x.isDead()).parallel().forEach(snake->{
            snake.look();
            snake.think();
            snake.move();
        });
    }

    public void load(Matrix[] w) {
        Arrays.stream(snakes).parallel().forEach(x->x.brain.load(w));
        bestSnake = snakes[0].clone();
    }

    public void show() {  //show either the best snake or all the snakes
        if (replayBest) {
            bestSnake.show();
            cam.beginHUD();
            bestSnake.brain.show(0, 0, applet.width/5, applet.height-SIZE, bestSnake.vision, bestSnake.decision);  //show the brain of the best snake
            cam.endHUD();
        } else {
            Arrays.stream(snakes).filter(Snake::isLive).forEach(Snake::show);
            cam.beginHUD();
            Arrays.stream(snakes).parallel().filter(Snake::isLive).findFirst().ifPresent(snake -> snake.brain.show(0, 0, applet.width/5, applet.height-SIZE, bestSnake.vision, bestSnake.decision));
            cam.endHUD();
        }
    }

    public void setBestSnake() {  //set the best snake of the generation
        int maxIndex = IntStream.range(0, snakes.length)
                .parallel()  // 并行化处理
                .boxed()     // 将 int 转为 Integer 对象，便于后续处理
                .max((i, j) -> Float.compare(snakes[i].fitness, snakes[j].fitness))  // 找到 fitness 最大的索引
                .orElse(0);  // 如果没有元素，默认返回 0

        float max = snakes[maxIndex].fitness;  // 根据 maxIndex 找到对应的最大 fitness
        if (max > bestFitness) {
            bestFitness = max;
            bestSnake = snakes[maxIndex].cloneForReplay();
            bestSnakeScore = snakes[maxIndex].score;
            //samebest = 0;
            //mutationRate = defaultMutation;
        } else {
            bestSnake = bestSnake.cloneForReplay();
      /*
         samebest++;
       if(samebest > 2) {  //if the best snake has remained the same for more than 3 generations, raise the mutation rate
       mutationRate *= 2;
       samebest = 0;
       }*/
        }
    }

    public Snake selectParent() {  //selects a random number in range of the fitnesssum and if a snake falls in that range then select it
        float rand = applet.random(fitnessSum);
        float summation = 0;
        for (Snake snake : snakes) {
            summation += snake.fitness;
            if (summation > rand) {
                return snake;
            }
        }
        return snakes[0];
    }

    public void naturalSelection() {
        Snake[] newSnakes = new Snake[snakes.length];
        setBestSnake();
        calculateFitnessSum();
        newSnakes[0] = bestSnake.clone();  //add the best snake of the prior generation into the new generation
        IntStream.range(1, snakes.length).parallel().forEach(i->{
            var child = selectParent().crossover(selectParent());
            child.mutate();
            newSnakes[i] = child;
        });
        snakes = newSnakes.clone();
        evolution.add(bestSnakeScore);
        gen += 1;
    }

    public void mutate() {
        Arrays.stream(snakes).parallel().forEach(Snake::mutate);
    }

    public void calculateFitness() {  //calculate the fitnesses for each snake
        Arrays.stream(snakes).parallel().forEach(Snake::calculateFitness);
    }

    public void calculateFitnessSum() {  //calculate the sum of all the snakes fitnesses
        fitnessSum = (float) Arrays.stream(snakes).parallel().mapToDouble(Snake::getFitness).sum();
    }

    public Integer getLives() {
        return Arrays.stream(snakes).parallel().mapToInt(s->s.isLive() ? 1 : 0).sum();
    }
}
